Map (higher-order function)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
top
In many programming languages, map is a higher-order function that applies a given function to each element of a collection, e.g. a list or set, returning the results in a collection of the same type. It is often called apply-to-all when considered in functional form.
The concept of a map is not limited to lists: it works for sequential containers, tree-like containers, or even abstract containers such as futures and promises.
Contents
β’ Visual example
β’ Generalization
β’ Optimizations
β’ See also
β’ References
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Examples: mapping a list
square x = x * x
Afterwards, call:
>>> map square [1, 2, 3, 4, 5]
which yields [1, 4, 9, 16, 25], demonstrating that map has gone through the entire list and applied the function square to each element.
Visual example
Below, there is view of each step of the mapping process for a list of integers X = [0, 5, 8, 3, 2, 1] mapping into a new list X' according to the function f ( x ) = x + 1 {\displaystyle f(x)=x+1} :
The map is provided as part of the Haskell's base prelude (i.e. "standard library") and is implemented as:
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x : xs) = f x : map f xs
Generalization
In Haskell, the polymorphic function map :: (a -> b) -> [a] -> [b] is generalized to a polytypic function fmap :: Functor f => (a -> b) -> f a -> f b, which applies to any type belonging the Functor type class.
The type constructor of lists [] can be defined as an instance of the Functor type class using the map function from the previous example:
instance Functor [] where
fmap = map
Other examples of Functor instances include trees:
-- a simple binary tree
data Tree a = Leaf a | Fork (Tree a) (Tree a)
instance Functor Tree where
fmap f (Leaf x) = Leaf (f x)
fmap f (Fork l r) = Fork (fmap f l) (fmap f r)
Mapping over a tree yields:
>>> fmap square (Fork (Fork (Leaf 1) (Leaf 2)) (Fork (Leaf 3) (Leaf 4)))
Fork (Fork (Leaf 1) (Leaf 4)) (Fork (Leaf 9) (Leaf 16))
For every instance of the Functor type class, fmap is contractually obliged to obey the functor laws:
fmap id β‘ id -- identity law
fmap (f . g) β‘ fmap f . fmap g -- composition law
where . denotes function composition in Haskell.
Among other uses, this allows defining element-wise operations for various kinds of collections.
Category-theoretic background
In category theory, a functor F : C β D {\displaystyle F:C\rightarrow D} consists of two maps: one that sends each object A of the category to another object F A, and one that sends each morphism f : A β B {\displaystyle f:A\rightarrow B} to another morphism F f : F A β F B {\displaystyle Ff:FA\rightarrow FB} , which acts as a homomorphism on categories (i.e. it respects the category axioms). Interpreting the universe of data types as a category Type, with morphisms being functions, then a type constructor F that is a member of the Functor type class is the object part of such a functor, and fmap :: (a -> b) -> F a -> F b is the morphism part. The functor laws described above are precisely the category-theoretic functor axioms for this functor.
Functors can also be objects in categories, with "morphisms" called natural transformations. Given two functors F , G : C β D {\displaystyle F,G:C\rightarrow D} , a natural transformation Ξ· : F β G {\displaystyle \eta :F\rightarrow G} consists of a collection of morphisms Ξ· A : F A β G A {\displaystyle \eta _{A}:FA\rightarrow GA} , one for each object A of the category D, which are 'natural' in the sense that they act as a 'conversion' between the two functors, taking no account of the objects that the functors are applied to. Natural transformations correspond to functions of the form eta :: F a -> G a, where a is a universally quantified type variable β eta knows nothing about the type which inhabits a. The naturality axiom of such functions is automatically satisfied because it is a so-called free theorem, depending on the fact that it is parametrically polymorphic.cite-ref-1[1] For example, reverse :: List a -> List a, which reverses a list, is a natural transformation, as is flattenInorder :: Tree a -> List a, which flattens a tree from left to right, and even sortBy :: (a -> a -> Bool) -> List a -> List a, which sorts a list based on a provided comparison function.
Optimizations
The mathematical basis of maps allow for a number of optimizations. The composition law ensures that both
β’ (map f . map g) list and
β’ map (f . g) list
lead to the same result; that is, map β‘ ( f ) β map β‘ ( g ) = map β‘ ( f β g ) {\displaystyle \operatorname {map} (f)\circ \operatorname {map} (g)=\operatorname {map} (f\circ g)} . However, the second form is more efficient to compute than the first form, because each map requires rebuilding an entire list from scratch. Therefore, compilers will attempt to transform the first form into the second; this type of optimization is known as map fusion and is the functional analog of loop fusion.cite-ref-2[2]
Map functions can be and often are defined in terms of a fold such as foldr, which means one can do a map-fold fusion: foldr f z . map g is equivalent to foldr (f . g) z.
The implementation of map above on singly linked lists is not tail-recursive, so it may build up a lot of frames on the stack when called with a large list. Many languages alternately provide a "reverse map" function, which is equivalent to reversing a mapped list, but is tail-recursive. Here is an implementation which utilizes the fold-left function.
reverseMap f = foldl (\ys x -> f x : ys) []
Since reversing a singly linked list is also tail-recursive, reverse and reverse-map can be composed to perform normal map in a tail-recursive way, though it requires performing two passes over the list.
Language comparison
The map function originated in functional programming languages.
maplist[x;f] = [null[x] -> NIL;T -> cons[f[x];maplist[cdr[x];f]]]
The function maplist is still available in newer Lisps like Common Lisp,cite-ref-5[5] though functions like mapcar or the more generic map would be preferred.
Squaring the elements of a list using maplist would be written in S-expression notation like this:
(maplist (lambda (l) (sqr (car l))) '(1 2 3 4 5))
Using the function mapcar, above example would be written like this:
(mapcar (function sqr) '(1 2 3 4 5))
Today mapping functions are supported (or may be defined) in many procedural, object-oriented, and multi-paradigm languages as well: In C++'s Standard Library, it is called std::transform, in C# (3.0)'s LINQ library, it is provided as an extension method called Select. Map is also a frequently used operation in high level languages such as ColdFusion Markup Language (CFML), Perl, Python, and Ruby; the operation is called map in all four of these languages. A collect alias for map is also provided in Ruby (from Smalltalk). Common Lisp provides a family of map-like functions; the one corresponding to the behavior described here is called mapcar (-car indicating access using the CAR operation). There are also languages with syntactic constructs providing the same functionality as the map function.
Map is sometimes generalized to accept dyadic (2-argument) functions that can apply a user-supplied function to corresponding elements from two lists. Some languages use special names for this, such as map2 or zipWith. Languages using explicit variadic functions may have versions of map with variable arity to support variable-arity functions. Map with 2 or more lists encounters the issue of handling when the lists are of different lengths. Various languages differ on this. Some raise an exception. Some stop after the length of the shortest list and ignore extra items on the other lists. Some continue on to the length of the longest list, and for the lists that have already ended, pass some placeholder value to the function indicating no value.
In languages which support first-class functions and currying, map may be partially applied to lift a function that works on only one value to an element-wise equivalent that works on an entire container; for example, map square is a Haskell function which squares each element of a list.
| Language | Map |
|---|---|
| APL | func list |
| Common Lisp | (mapcar func list ) |
| C++ | std::transform( begin , end , result ,β¦ |
| C# | ienum .Select( func ) or The select cla⦠|
| CFML | obj.map(func) |
| Clojure | (map func list ) |
| D | list .map! func |
| Erlang | lists:map( Fun , List ) |
| Elixir | Enum.map( list , fun ) |
| F# | List.map func list |
| Gleam | list.map( list , func ) yielder.map( yi⦠|
| Groovy | list . collect ( func ) |
| Haskell | map func list |
| Haxe | array .map( func ) list .map( func ) La⦠|
| J | func list |
| Java 8+ | stream .map( func ) |
| JavaScript 1.6 ECMAScript 5 | array #map( func ) |
| Julia | map( func , list ) |
| Logtalk | map( Closure , List ) |
| Mathematica | func /@ list Map[ func , list ] |
| Maxima | map( f , expr 1 , ..., expr n ) maplist⦠|
| OCaml | List.map func list Array.map func array |
| PARI/GP | apply( func , list ) |
| Perl | map block list map expr , list |
| PHP | array_map( callable , array ) |
| Prolog | maplist( Cont , List1 , List2 ). |
| Python | map( func , list ) |
| Ruby | enum .collect { block } enum .map { blo⦠|
| Rust | list1 .into_iter().map( func ) |
| S - R | lapply( list , func ) |
| Scala | list .map( func ) |
| Scheme (including Guile and Racket ) | (map func list ) |
| Smalltalk | aCollection collect: aBlock |
| Standard ML | map func list |
| Swift | sequence .map( func ) |
| XPath 3 XQuery 3 | list ! block for-each ( list , func ) |
| Language | Map 2 lists |
|---|---|
| APL | list1 func list2 |
| Common Lisp | (mapcar func list1 list2 ) |
| C++ | std::transform( begin1 , end1 , begin2β¦ |
| C# | ienum1 .Zip( ienum2 , func ) |
| Clojure | (map func list1 list2 ) |
| D | zip( list1 , list2 ).map! func |
| Erlang | lists:zipwith( Fun , List1 , List2 ) |
| Elixir | Enum.zip( list1 , list2 ) /> Enum.map(f⦠|
| F# | List.map2 func list1 list2 |
| Gleam | list.map2( list1 , list2 , func ) yield⦠|
| Groovy | [ list1 list2 ]. transpose (). collect⦠|
| Haskell | zipWith func list1 list2 |
| J | list1 func list2 |
| JavaScript 1.6 ECMAScript 5 | List1 .map(function (elem1, i) { return⦠|
| Julia | map( func , list1, list2 ) |
| Logtalk | map( Closure , List1 , List2 ) |
| Mathematica | MapThread[ func , { list1 , list2 }] |
| OCaml | List.map2 func list1 list2 |
| PHP | array_map( callable , array1 , array2 ) |
| Prolog | maplist( Cont , List1 , List2 , List3 ). |
| Python | map( func , list1 , list2 ) |
| Ruby | enum1 .zip( enum2 ) .map { block } |
| Rust | list1 .into_iter().zip( list2 ).map( fu⦠|
| S - R | mapply( func , list1 , list2 ) |
| Scala | ( list1 , list2 ) .zipped.map( func ) |
| Scheme (including Guile and Racket ) | (map func list1 list2 ) |
| Smalltalk | aCollection1 with: aCollection2 collect⦠|
| Standard ML | ListPair.map func ( list1 , list2 ) Lis⦠|
| Swift | zip( sequence1 , sequence2 ).map( func ) |
| XPath 3 XQuery 3 | for-each-pair ( list1 , list2 , func ) |
| Language | Map n lists |
|---|---|
| APL | func / list1 list2 list3 list4 |
| Common Lisp | (mapcar func list1 list2 ...) |
| Clojure | (map func list1 list2 ...) |
| D | zip( list1 , list2 , ...).map! func |
| Erlang | zipwith3 also available |
| Elixir | List.zip([ list1 , list2 , ...]) /> Enu⦠|
| Groovy | [ list1 list2 ...]. transpose (). colle⦠|
| Haskell | zipWith n func list1 list2 ... |
| J | func / list1 , list2 , list3 ,: list4 |
| JavaScript 1.6 ECMAScript 5 | List1 .map(function (elem1, i) { return⦠|
| Julia | map( func , list1, list2, ..., listN ) |
| Logtalk | map( Closure , List1 , List2 , List3 ,β¦ |
| Mathematica | MapThread[ func , { list1 , list2 , ...β¦ |
| PHP | array_map( callable , array1 , array2 ,β¦ |
| Prolog | maplist( Cont , List1 , ... ). |
| Python | map( func , list1 , list2 , ...) |
| Ruby | enum1 .zip( enum2 , ...) .map { block }β¦ |
| S - R | mapply( func , list1 , list2 , ...) |
| Scala | ( list1 , list2 , list3 ) .zipped.map(β¦ |
| Scheme (including Guile and Racket ) | (map func list1 list2 ...) |
| Language | Notes |
|---|---|
| APL | APL's array processing abilities make o⦠|
| C++ | in header <algorithm> begin , end , and⦠|
| C# | Select is an extension method ienum is⦠|
| CFML | Where obj is an array or a structure. f⦠|
| F# | Functions exist for other types ( Seq a⦠|
| Haskell | n corresponds to the number of lists; p⦠|
| J | J's array processing abilities make ope⦠|
| JavaScript 1.6 ECMAScript 5 | Array#map passes 3 arguments to func :β¦ |
| Logtalk | Only the Closure argument must be insta⦠|
| Maxima | map returns an expression which leading⦠|
| Perl | In block or expr special variable $_ ho⦠|
| PHP | The number of parameters for callable s⦠|
| Prolog | List arguments are input, output or bot⦠|
| Python | Returns a list in Python 2 and an itera⦠|
| Ruby | enum is an Enumeration |
| Rust | the Iterator::map and Iterator::zip met⦠|
| Scala | note: more than 3 not possible. |
| Standard ML | For 2-argument map, func takes its argu⦠|
| XPath 3 XQuery 3 | In block the context item . holds the c⦠|
| Language | Handling lists of different lengths |
|---|---|
| APL | length error if list lengths not equal⦠|
| Common Lisp | stops after the length of the shortest⦠|
| C# | stops after the shortest list ends |
| Clojure | stops after the shortest list ends |
| D | Specified to zip by StoppingPolicy: sho⦠|
| Erlang | Lists must be equal length |
| Elixir | stops after the shortest list ends |
| F# | Throws exception |
| Gleam | drops the extra elements of the longer⦠|
| Haskell | stops after the shortest list ends |
| J | length error if list lengths not equal |
| JavaScript 1.6 ECMAScript 5 | Stops at the end of List1 , extending t⦠|
| Julia | ERROR: DimensionMismatch |
| Logtalk | Failure |
| Mathematica | Lists must be same length |
| OCaml | raises Invalid_argument exception |
| PARI/GP | β |
| Perl | Helper List::MoreUtils::each_array comb⦠|
| PHP | extends the shorter lists with NULL ite⦠|
| Prolog | Silent failure (not an error) |
| Python | zip() and map() (3.x) stops after the s⦠|
| Ruby | stops at the end of the object it is ca⦠|
| Rust | stops after the shorter list ends |
| S - R | Shorter lists are cycled |
| Scala | stops after the shorter list ends |
| Scheme (including Guile and Racket ) | lists must all have same length (SRFI-1β¦ |
| Smalltalk | Fails |
| Standard ML | ListPair.map stops after the shortest l⦠|
| Swift | stops after the shortest list ends |
| XPath 3 XQuery 3 | stops after the shortest list ends |
See also
β’ Zipping (computer science) or zip, mapping 'list' over multiple lists
β’ foreach loop
β’ Free monoid
References
cite-note-11. β In a non-strict language that permits general recursion, such as Haskell, this is only true if the first argument to fmap is strict. citerefwadler1989Wadler, Philip (September 1989). Theorems for free! (PDF). 4th International Symposium on Functional Programming Languages and Computer Architecture. London: Association for Computing Machinery.
cite-note-22. β "Map fusion: Making Haskell 225% faster"
cite-note-33. β J. McCarthy, K. Maling, S. Russell, N. Rochester, S. Goldberg, J. Slagle. LISP Programmer's Manual. March-April, 1959
cite-note-44. β J. McCarthy: Symbol Manipulating Language - Revisions of the Language. AI Memo No. 4, October 1958
cite-note-55. β Function MAPC, MAPCAR, MAPCAN, MAPL, MAPLIST, MAPCON in ANSI Common Lisp